Big Web Services with Java

Web services就是客户端通过HTTP协议向server发送请求,并获得所需要的信息或服务。Web services可以看做一种处于不同机器不同平台上的软件进行交互的一种标准方式。

目前主要有两种类型的Web services:
一种是所谓的big web services,因为其相对于第二种方式比较重量级。big web services使用XML封装请求信息以及回答,按照Simple Object Asscess Protocol,所以也称为SOAP消息,提供方法信息和作用域信息,另外使用的一项技术是WSDL(web service description language),web服务描述语言,是一套用于描述SOAP WEB服务的XML词汇。客户端可以通过参考WSDL文档,可以确切的知道可以调用哪些RPC式方法,需要哪些参数,以及返回值数据类型。SOAP的服务都发布有WSDL文档,否则很难使用。对于很多程序语言,WSDL也是用来自动生成客户端stub必须的文档。

另一种是RESTful webservice,具体两种实现的区别请参考Web Service的Programmable Web及其分类。这里主要介绍基于java实现big web service.

Big web service的基本技术

SOAP

SOAP是Web Service的基本通信协议,是一种规范,用来定义SOAP消息的XML格式(XMLFormat)。包含在一对 SOAP 元素(SOAPElements)中的、结构正确的 XML 段就是 SOAP 消息。
SOAP 规范还介绍了如何将程序数据表示为 XML,以及如何使用 SOAP 进行远程过程调用 (RPC)。
最后SOAP规范还定义了HTTP消息是怎样传输SOAP消息的。这并不代表SOAP只能用HTTP来作为传输协议,MSMQ、SMTP、TCP/IP都可以做SOAP的传输协议。

WSDL

Web Services Description Language的缩写,是一个用来描述Web服务和说明如何与Web服务通信的XML语言。WSDL是Web Service的描述语言,用于描述Web Service的服务,接口绑定等,为用户提供详细的接口说明书。
举个例子,你要使用供应商的WebService构建应用程序。你可以向供应商索取使用Web Service的范例,然后按照范例来构建应用程序。这样可能出现意料不到的错误,比如说,你在程序中使用的客户代码的数据类型是integer,而供应商使用的数据类型是string。WSDL详细定义客户端消息的格式,需要什么样的参数,这样可以避免不必要的错误。
要查看 WSDL 的值,可以假设您要调用由您的一位业务伙伴提供的 SOAP 方法。您可以要求对方提供一些 SOAP 消息示例,然后编写您的应用程序以生成并使用与示例类似的消息。WSDL 通过明确的表示法指定请求消息必须包含的内容以及响应消息的样式。
WSDL 文件用于说明消息格式的表示法以 XML 架构标准为基础,这意味着它与编程语言无关,而且以标准为基础,因此适用于说明可从不同平台、以不同编程语言访问的 XML Web Service 接口。除说明消息内容外,WSDL 还定义了服务的位置,以及使用什么通信协议与服务进行通信。WSDL 文件定义了编写使用 XML Web Service 的程序所需的全部内容。

Google搜索服务的WSDL文档片断:

1
2
3
4
<operation name="doGoogleSearch">
<input message="typens:doGoogleSearch">
<output message="typens:doGoogleSearchRespnse">
</operation>

一个SOAP RPC调用的HTTP请求

1
2
3
4
5
6
7
8
9
10
11
12
POST search/beta2 HTTP/1.1
Host:...
...

<?xml version="1.0"...>
<soap:Envelop xmln:soap="UTF-8">
<soap:body>
<gs:doGoogleSearch>
<q>plusaber</q>
</gs:doGoogleSearch>
</soap:Body>
</soap:Envelope>

UDDI

UniversalDescription Discovery and Integration即统一描述、发现和集成协议。UDDI实现了一组可公开访问的接口,通过这些接口,网络服务可以向服务信息库注册其服务信息、服务需求者可以找到分散在世界各地的网络服务。
UDDI 目录条目是介绍所提供的业务和服务的 XML 文件。可以把它比喻成电话本,电话本里记录的是电话信息,而UDDI记录的是Web Service信息。你可以不把Web Service注册到UDDI。但如果要让全球的人知道你的Web Service,最好还是注册到UDDI。
UDDI 目录还包含若干种方法,可用于搜索构建您的应用程序所需的服务。例如,您可以搜索特定地理位置的服务提供商或者搜索特定的业务类型。之后,UDDI 目录将提供信息、联系方式、链接和技术数据,以便您确定能满足需要的服务。
UDDI 允许您查找提供所需的Web 服务的公司。如果您已经知道要与谁进行业务合作,但尚不了解它还能提供哪些服务,这时该如何处理呢?WS-Inspection规范允许您浏览特定服务器上提供的 XML Web Service 的集合,从中查找所需的服务。

Big Web Service的Java实现

Web Service本质是一种标准,具体每种语言都提供了不同的实现。在Java领域,Big Web Service的标准是JAX-WS,实现框架有:CXF, axis2, Java 6开始自带的Web Service引擎(自带的JAX-WS implementation)。

JAX-WS的不同实现框架

这里先说明一下为什么存在这些不同的实现框架,特别是Java 6开始自带的Web Service引擎与其他更复杂的框架,参考Difference between Jax-ws, axis2, cxf
The JAX-WS implementation built into the JDK really is just the basic soap stuff. If you need any of the more complex WS-* things like WS-Security, WS-RM, WS-Policy, etc…, you need to use one of the alternatives like CXF or Metro or Axis2. It can also depend on what you are trying to integrate with. For example, CXF has top notch Spring support as well as very good OSGi support.

CXF also has other things besides just JAX-WS. It has a compliant JAX-RS implementation as well and supports exposing services as both REST and SOAP very well. Has a W3C compliant SOAP/JMS implementation if that type of things is required. Basically, lots of stuff not available from the in-jdk JAX-WS impl.

Also see:
Difference between Apache CXF and Axis

也就是Java自带的Web Service引擎提供了SOAP通信的服务,但是不支持更高级的需求,比如Web Service安全性,与Spring结合等。

创建和使用Web Service基本步骤

  1. 编写服务的接口
  2. 编写服务接口的实现类
  3. 发布
  4. 测试或使用

使用JAX-WS(Java自带)实现第一个Web Service

项目结构如下:

WebService_1

  • 编写服务接口HelloWorld
1
2
3
4
5
6
7
8
9
10
11
package jws;

import javax.jws.WebParam;
import javax.jws.WebService;

@WebService
public interface HelloWorld {

public String sayHello(@WebParam(name="who") String who);

}

注意@WebService是必须要有的,用于指定这是一个Web Service服务类。

另外要暴露给客户端的方法需要注明为@WebMethod。@WebMethod是用来标注在WebService的方法即操作上的,通过它我们可以指定某一个操作的操作名、使用SOAP绑定时的Action名称,以及该方法是否允许发布为WebService。@WebMethod有三个属性:operationName、action和exclude。如果接口中方法不希望作为WebService访问的时候就可以通过@WebMethod来指定将其排除在外(@WebMethod(exclude=true))。

@Oneway是标注在Service接口的操作方法上的。使用@Oneway标注的方法表示它不需要等待服务端的返回,也不需要预留资源来处理服务端的返回。

因为接口在被编译为class文件的时候不能保存参数名,有时候这会影响可读性。如果需要参数名显示的可读性强一些的话,我们可以使用@WebParam来指定。

  • 编写服务接口HelloWorld实现类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package jws;

import javax.jws.WebMethod;
import javax.jws.WebService;

// endpointInterface用于指定实现的接口
// 同时也设定了服务的targetName和name,后面client定位这个服务时需要用到

@WebService(endpointInterface = "jws.HelloWorld")
public class HelloWorldImpl implements HelloWorld {

@Override
@WebMethod(exclude=true)
public String sayHello(String who) {
return"Hello, " + who;
}

}

javax.xml.ws.WebServiceException: Undefined port type Java Struts SOAP WSDL

  • 发布服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package jws;

import javax.xml.ws.Endpoint;


public class ServerTest {

public static void deployService() {
System.out.println("Server start ……");
HelloWorld service = new HelloWorldImpl();
String address = "http://localhost:9999/ws/hello";
Endpoint.publish(address, service);
}

public static void main(String[] args) throws InterruptedException {
deployService();
System.out.println("server ready ……");
Thread.sleep(1000 * 60);
System.out.println("server exiting");
System.exit(0);
}
}
  • (可跳过)验证服务

打开浏览器,输入http://localhost:9999/ws/hello?wsdl,可以验证服务的WSDL文件(mac的safari不显示,可以用Chrome验证)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!--
Published by JAX-WS RI (http://jax-ws.java.net). RI's version is JAX-WS RI 2.2.9-b130926.1035 svn-revision#5f6196f2b90e9460065a4c2f4e30e065b245e51e.
-->
<!--
Generated by JAX-WS RI (http://jax-ws.java.net). RI's version is JAX-WS RI 2.2.9-b130926.1035 svn-revision#5f6196f2b90e9460065a4c2f4e30e065b245e51e.
-->
<definitions xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:wsp="http://www.w3.org/ns/ws-policy" xmlns:wsp1_2="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://jws/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.xmlsoap.org/wsdl/" targetNamespace="http://jws/" name="HelloWorldImplService">
<types>
<xsd:schema>
<xsd:import namespace="http://jws/" schemaLocation="http://localhost:9999/ws/hello?xsd=1"/>
</xsd:schema>
</types>
<message name="sayHello">
<part name="parameters" element="tns:sayHello"/>
</message>
<message name="sayHelloResponse">
<part name="parameters" element="tns:sayHelloResponse"/>
</message>
<portType name="HelloWorld">
<operation name="sayHello">
<input wsam:Action="http://jws/HelloWorld/sayHelloRequest" message="tns:sayHello"/>
<output wsam:Action="http://jws/HelloWorld/sayHelloResponse" message="tns:sayHelloResponse"/>
</operation>
</portType>
<binding name="HelloWorldImplPortBinding" type="tns:HelloWorld">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
<operation name="sayHello">
<soap:operation soapAction=""/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
</binding>
<service name="HelloWorldImplService">
<port name="HelloWorldImplPort" binding="tns:HelloWorldImplPortBinding">
<soap:address location="http://localhost:9999/ws/hello"/>
</port>
</service>
</definitions>
  • 编写客户端,调用服务

需要利用工具类生成并导入服务的接口类HelloWorld

method 1: 使用Eclipse工具生成
method 2: 通过cxf的命令生成客户端代码。
进入apache-cxf-2.3.1\bin所在目录,输入命令:

1
wsdl2java -p com.jaxb.client -d . http://localhost:9999/ws/hello?wsdl

命令格式为:wsdl2java –p 包名 –d 生成代码存放目录 wsdl的url

wsdl2java用法:

param usage
-p 指定其wsdl的命名空间,也就是要生成代码的包名;
-d 指定要产生代码所在目录;
-client 生成客户端测试web service的代码;
-server 生成服务器启动web service的代码;
-impl 生成web service的实现代码;
-ant 生成build.xml文件;
-all 生成所有开始端点代码:types,serviceproxy, service interface, server mainline, client mainline, implementation object,and an Ant build.xml file。

另外还需要知道服务wsdl文件中的targetNamespace和name用于定位服务。

targetNamespace="http://jws/" name="HelloWorldImplService

将生成代码拷贝到client代码目录下或导入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package client;

import java.net.MalformedURLException;
import java.net.URL;

import javax.xml.namespace.QName;
import javax.xml.ws.Service;

import jws.HelloWorld;

public class Client {
public static void main(String[] args) {
URL url = null;
try {
url = new URL("http://localhost:9999/ws/hello?wsdl");
} catch (MalformedURLException e) {
java.util.logging.Logger.getLogger(HelloWorld.class.getName())
.log(java.util.logging.Level.INFO,
"Can not initialize the default wsdl from {0}", "http://localhost:9000/helloWorld?wsdl");
}

QName qname = new QName("http://jws/", "HelloWorldImplService");

Service service = Service.create(url, qname);

HelloWorld hw = service.getPort(HelloWorld.class);
System.out.println(hw.sayHello("World"));
}
}
1
Hello, World

上面是基本的基于Java 6开始自带的Web Service引擎的helloworld实现,没有使用任何其他包,例如CFX。

HelloWrold基于CXF的实现

基本流程和上面一致。

使用CXF的JaxWsServerFactoryBean类进行发布。

1
2
3
4
5
6
7
HelloServiceImpl impl = new HelloServiceImpl();
JaxWsServerFactoryBean factoryBean = newJaxWsServerFactoryBean();
factoryBean.setAddress("http://localhost:8080/WSCXF/helloService");
factoryBean.setServiceClass(IHelloService.class);//接口类
factoryBean.setServiceBean(impl);
factoryBean.create();
System.out.println("WS发布成功!");

这两种方法都是将服务发布到Jetty下。另外还可以利用cxf和spring整合在tomcat下进行发布。

客户端使用cxf的调用服务:

1
2
3
4
5
JaxWsProxyFactoryBean soapFactoryBean = newJaxWsProxyFactoryBean();
soapFactoryBean.setAddress("http://localhost:8080/WSCXF/helloService");
soapFactoryBean.setServiceClass(IHelloService.class);
Object o = soapFactoryBean.create();
IHelloService helloService = (IHelloService)o;

可以看出Big web service的生成发布以及调用过程都是基本一致的,只是在各个步骤可以使用不同的框架和实现,不同框架提供了不同程度的安全性,整合等支持。

CXF和Spring整合

参考JAX-WS Hello World Example – Document Style